AWS SDK for Javaを使う#Amazon DynamoDB
はじめに
いまやクラウドサービスの代表格とも言えるAmazonですが、EC2やS3をはじめとして、さまざまなサービスを提供しています。 数年前に私も少しだけEC2やS3を使用したことがあるのですが、最近はあまりさわっていませんでした。 しかし今回AWSについての調査をきっかけに、各種AWSサービスについて復習&AWS SDKでの動作確認をしていきたいと思います。
今回はNoSQLの高速データベース、Amazon DynamoDBです。
今回使用した動作環境は以下のとおりです。
- OS : MacOS X 10.7.2
- Java : 1.6.0_26
- Scala : 2.9.1 final
- SBT : 0.11.2
なお、AWSへの登録は終わっているものとします。
Amazon DynamoDB
Amazon DynamoDBについての解説はここに詳しく書いてあるので詳細は省きます。 SimpleDBとかぶる部分もありますが、大量データ時のパフォーマンスや容量についてはDynamoのほうが優れているようです。 それではプログラムからDynamoDBにアクセスしてみましょう。
実行環境のセットアップ
以前と同じくsbt + scala + aws-sdk-javaをセットアップしておきましょう。
Amazon DynamoDBサンプル作成
まずは初期処理です。いつものようにキーを指定してAmazonDynamoDBClientオブジェクトを作成しています。
// アクセスキー val accessKey = "アクセスキー" // シークレットキー val secretKey = "シークレットキー" val credentials = new BasicAWSCredentials(accessKey,secretKey) val dynamoDB = new AmazonDynamoDBClient(credentials)
次にテーブルを作成します。テーブルの作成はCreateTableRequestにテーブル名やキーの設定を行います。 ここではMyUserという名前のテーブルを、プライマリーキーはidという名前で作成しています。 createTableメソッドを呼べばテーブル作成のリクエストが送信されます。
//テーブル定義 val tableName = "MyUser" val createTableRequest = new CreateTableRequest() .withTableName(tableName) .withKeySchema(new KeySchema(new KeySchemaElement() .withAttributeName("id") .withAttributeType("N"))) .withProvisionedThroughput( new ProvisionedThroughput() .withReadCapacityUnits(10L) .withWriteCapacityUnits(10L)) val createdTableDescription = dynamoDB.createTable(createTableRequest).getTableDescription() println("Created Table: " + createdTableDescription)
テーブルの作成ができたらテーブル情報を確認してみましょう。 describeTableメソッドを呼べば、テーブル情報を取得できます。
val describeTableRequest = new DescribeTableRequest().withTableName(tableName) val tableDescription = dynamoDB.describeTable(describeTableRequest).getTable() println("Table Description: " + tableDescription)
次はデータの登録です。HashMapを使用してputItemメソッドを呼べば簡単に登録することができます。
//データの登録 val item = Map("id" -> new AttributeValue().withN(Integer.toString(1)), "name" -> new AttributeValue("taro"), "age" -> new AttributeValue("20")) val putItemRequest = new PutItemRequest(tableName, item) val putItemResult = dynamoDB.putItem(putItemRequest) println("Result: " + putItemResult)
登録ができたら次は検索です。検索はScanRequestオブジェクトにテーブル名とフィルター用マップを渡します。 フィルター用マップにはConditionオブジェクトが設定されています。 Conditionオブジェクトは、条件とその値を設定し、マップのキーにフィールド名を指定しています。
//IDが1のものを検索 val condition = new Condition() .withComparisonOperator(ComparisonOperator.EQ.toString()) //=である .withAttributeValueList(new AttributeValue().withN(Integer.toString(1))) //数値の1である //検索条件のフィールドを指定 val scanFilter = Map("id" -> condition) val scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter) val scanResult = dynamoDB.scan(scanRequest) println("Scan Result: " + scanResult)
これで基本的な更新と検索ができるのですが、DynamoDBはDynamoDBMapperというクラスを提供しています。 これはモデルにアノテーションを付与することでORマッパーチックに使用できます。 では、MyUserモデルを作成して登録してみましょう。
@DynamoDBTable(tableName = "MyUser") case class MyUser { var id:Int = _ var name:String = _ def setId(_id:Int) = id = _id def setName(_name:String) = name = _name @DynamoDBHashKey(attributeName = "id") def getId = id @DynamoDBAttribute(attributeName = "name") def getName = name } //DMMapperの作成 val dynamoMapper = new DynamoDBMapper(dynamoDB) //DataMapperの登録 val user = new MyUser() user.setId(100) user.setName("m-user") dynamoMapper.save(user)
DynamoDBTableアノテーションを使用してテーブルとクラスを関連付け、プロパティのgetterにもキーや属性のアノテーションを指定します。 saveメソッドにオブジェクトを渡せば、登録することができます。 ただ、このサンプルはScalaを使用しているので、モデルの記述がかなり冗長になっています。
次にDBMapperを使用した検索です。検索はloadメソッドを使用し、マップするオブジェクトとキーを渡して検索しています。
//DataMapperでidが100のものを検索 val u = dynamoMapper.load(classOf[MyUser], "100") println("mapper id=: " + u.getId) println("mapper name=: " + u.getName)
最後にサンプル全文をのせておきます。一度目の実行でテーブルを作成し、2度目の実行でデータ登録と検索を行います。 ※1度目の実行後、しばらく時間がたたないと、テーブルができていないのでデータ登録が失敗する可能性があります
import com.amazonaws.auth.BasicAWSCredentials import com.amazonaws.auth.PropertiesCredentials import com.amazonaws.services.dynamodb.AmazonDynamoDBClient import com.amazonaws.services.dynamodb.model._ import com.amazonaws.services.dynamodb.datamodeling._ import com.amazonaws.AmazonServiceException import scala.collection.JavaConversions._ import scala.reflect._ object DynamoMain extends App{ /** * 指定した名前のテーブルがあるかどうか */ def isTable(tableName:String) :Boolean = { try{ val request = new DescribeTableRequest().withTableName(tableName) val tableDescription = dynamoDB.describeTable(request).getTable() val tableStatus = tableDescription.getTableStatus() println("tableStatus =" + tableStatus) true }catch { case e:AmazonServiceException => println(e.toString);false } } // アクセスキー val accessKey = "アクセスキー" // シークレットキー val secretKey = "シークレットキー" val credentials = new BasicAWSCredentials(accessKey,secretKey) val dynamoDB = new AmazonDynamoDBClient(credentials) val dynamoMapper = new DynamoDBMapper(dynamoDB) //テーブル定義 val tableName = "MyUser" if(isTable(tableName)) { println("テーブルが存在するのでデータの登録と検索をします") val describeTableRequest = new DescribeTableRequest().withTableName(tableName) val tableDescription = dynamoDB.describeTable(describeTableRequest).getTable() println("Table Description: " + tableDescription) //通常の登録 val item = Map("id" -> new AttributeValue().withN(Integer.toString(1)), "name" -> new AttributeValue("tarosss"), "age" -> new AttributeValue("20")) val putItemRequest = new PutItemRequest(tableName, item) val putItemResult = dynamoDB.putItem(putItemRequest) println("Result: " + putItemResult) //DataMapperの登録 val user = new MyUser() user.setId(100) user.setName("m-user") dynamoMapper.save(user) //IDが1のものを検索 val condition = new Condition() .withComparisonOperator(ComparisonOperator.EQ.toString()) .withAttributeValueList(new AttributeValue().withN(Integer.toString(1))) val scanFilter = Map("id" -> condition) val scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter) val scanResult = dynamoDB.scan(scanRequest) println("Scan Result: " + scanResult) //DataMapperでidが100のものを検索 val u = dynamoMapper.load(classOf[MyUser], "100") println("mapper id=: " + u.getId) println("mapper name=: " + u.getName) } else { println("テーブルがないので作成します") val createTableRequest = new CreateTableRequest().withTableName(tableName) .withKeySchema(new KeySchema(new KeySchemaElement().withAttributeName("id").withAttributeType("N"))) .withProvisionedThroughput(new ProvisionedThroughput().withReadCapacityUnits(10L).withWriteCapacityUnits(10L)) val createdTableDescription = dynamoDB.createTable(createTableRequest).getTableDescription() println("Created Table: " + createdTableDescription) } } @DynamoDBTable(tableName = "MyUser") case class MyUser { var id:Int = _ var name:String = _ def setId(_id:Int) = id = _id def setName(_name:String) = name = _name @DynamoDBHashKey(attributeName = "id") def getId = id @DynamoDBAttribute(attributeName = "name") def getName = name }
実行するにはいつものようにsbtを起動してrunコマンドを実行してください。
% sbt > run
まとめ
今回はDynamoDBを使用してみました。 まだベータですが、simpelDBに代わる高速なデータベースということで、今後も使用する機会は多いと思います。
参考サイトなど
- AWS SDK for Java javadoc: http://docs.amazonwebservices.com/AWSJavaSDK/latest/javadoc/
- Amazon DynamoDB公式ドキュメントリンク: http://aws.amazon.com/jp/documentation/dynamodb/